package com.ruoyi.web.Tool.BT;

import cn.hutool.core.io.FileUtil;
import com.dampcake.bencode.Bencode;
import com.dampcake.bencode.Type;
import com.ruoyi.web.domain.BT.TorrentDto;
import com.ruoyi.web.domain.BT.TorrentFileVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.system.ApplicationHome;

import java.io.*;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;

/**
 * @author Jrx
 */
@Slf4j
public class TorrentUtils {
    private static final char[] HEX_SYMBOLS = "0123456789ABCDEF".toCharArray();

    /**
     * 只获取种子info的hash并返回
     *
     * @param data
     * @return
     * @throws NoSuchAlgorithmException
     */
    public static TorrentDto getTorrentHash(byte[] data) throws NoSuchAlgorithmException {
        Bencode bencode = new Bencode(true);
        Map<String, Object> torrentDataMap = bencode.decode(data, Type.DICTIONARY);
        Map<String, Object> infoMap = (Map<String, Object>) torrentDataMap.get("info");
        List<Object> files = (List<Object>) infoMap.get("files");
        long len = 0;
        long count = 0;
        if (files != null) {
            for (Object o : files) {
                Map<String, Object> pace = (Map<String, Object>) o;
                len += (long) pace.get("length");
                List<Object> path = (List<Object>) pace.get("path");
                for (Object value : path) {
                    ByteBuffer name = (ByteBuffer) value;
                    String s = new String(name.array());
                    if (s.contains(".")) {
                        count++;
                    }
                }
            }
        } else {
            len = (long) infoMap.get("length");
            count = 1;
        }
        TorrentDto torrent = new TorrentDto();
        torrent.setUkInfoHash(hash(bencode.encode(infoMap)));
        torrent.setTorrentSize(len);
        torrent.setTorrentCount(count);
        return torrent;
    }

    public static TorrentFileVo getTorrentFile(File file) throws NoSuchAlgorithmException {
        TorrentFileVo torrent = new TorrentFileVo();
        torrent.setFillName(file.getName());
        Bencode bencode = new Bencode(true);
        byte[] fileBytes = FileUtil.readBytes(file);
        Map<String, Object> torrentMap = bencode.decode(fileBytes, Type.DICTIONARY);
        ByteBuffer announceByteBuffer = (ByteBuffer) torrentMap.get("announce");
        torrent.setAnnounce(new String(announceByteBuffer.array()));
        Map<String, Object> infoMap = (Map<String, Object>) torrentMap.get("info");
        // info
        List<Object> files = (List<Object>) infoMap.get("files");
        // hash
        torrent.setHash(hash(bencode.encode(infoMap)));
        // length
        long len = 0;
        if (files != null) {
            for (Object o : files) {
                Map<String, Object> pace = (Map<String, Object>) o;
                len += (long) pace.get("length");
            }
        } else {
            len = (long) infoMap.get("length");
        }
        torrent.setLength(len);
        return torrent;
    }


    /**
     * hash
     *
     * @param data
     * @return
     * @throws NoSuchAlgorithmException
     */
    public static byte[] hash(byte[] data) throws NoSuchAlgorithmException {
        MessageDigest crypt;
        crypt = MessageDigest.getInstance("SHA-1");
        crypt.reset();
        crypt.update(data);
        return crypt.digest();
    }

    /**
     * 获取默认种子目录: jar所在目录/torrent
     *
     * @return
     */
    public static String getDefaultDir() {
        ApplicationHome home = new ApplicationHome();
        return home.getDir().getAbsolutePath();
    }

    /**
     * 获取默认种子目录: jar所在目录/torrent
     *
     * @return
     */
    public static String getDefaultTorrentDir() {
        return getDefaultDir() + "/torrent/";
    }


    /**
     * 生成种子文件
     * {@code @author} Jia
     * {@code @date} 2025/5/27 17:20
     */

    private static void encodeObject(Object o, OutputStream out) throws IOException {
        if (o instanceof String)
            encodeString((String) o, out);
        else if (o instanceof Map)
            encodeMap((Map) o, out);
        else if (o instanceof byte[])
            encodeBytes((byte[]) o, out);
        else if (o instanceof Number)
            encodeLong(((Number) o).longValue(), out);
        else
            throw new Error("Unencodable type");
    }

    private static void encodeLong(long value, OutputStream out) throws IOException {
        out.write('i');
        out.write(Long.toString(value).getBytes("US-ASCII"));
        out.write('e');
    }

    private static void encodeBytes(byte[] bytes, OutputStream out) throws IOException {
        out.write(Integer.toString(bytes.length).getBytes("US-ASCII"));
        out.write(':');
        out.write(bytes);
    }

    private static void encodeString(String str, OutputStream out) throws IOException {
        encodeBytes(str.getBytes("UTF-8"), out);
    }

    private static void encodeMap(Map<String, Object> map, OutputStream out) throws IOException {
        // Sort the map. A generic encoder should sort by key bytes
        SortedMap<String, Object> sortedMap = new TreeMap<String, Object>(map);
        out.write('d');
        for (Map.Entry<String, Object> e : sortedMap.entrySet()) {
            encodeString(e.getKey(), out);
            encodeObject(e.getValue(), out);
        }
        out.write('e');
    }

    private static byte[] hashPieces(File file, int pieceLength) throws IOException {
        MessageDigest sha1;
        try {
            sha1 = MessageDigest.getInstance("SHA");
        } catch (NoSuchAlgorithmException e) {
            throw new Error("SHA1 not supported");
        }
        InputStream in = new FileInputStream(file);
        ByteArrayOutputStream pieces = new ByteArrayOutputStream();
        byte[] bytes = new byte[pieceLength];
        int pieceByteCount = 0, readCount = in.read(bytes, 0, pieceLength);
        while (readCount != -1) {
            pieceByteCount += readCount;
            sha1.update(bytes, 0, readCount);
            if (pieceByteCount == pieceLength) {
                pieceByteCount = 0;
                pieces.write(sha1.digest());
            }
            readCount = in.read(bytes, 0, pieceLength - pieceByteCount);
        }
        in.close();
        if (pieceByteCount > 0)
            pieces.write(sha1.digest());
        return pieces.toByteArray();
    }

    public static void createTorrent(File file, File sharedFile, String announceURL) throws IOException {
        final int pieceLength = 512 * 1024;
        Map<String, Object> info = new HashMap<>();
        info.put("name", sharedFile.getName());
        info.put("length", sharedFile.length());
        info.put("piece length", pieceLength);
        info.put("pieces", hashPieces(sharedFile, pieceLength));
        Map<String, Object> metainfo = new HashMap<String, Object>();
        metainfo.put("announce", announceURL);
        metainfo.put("info", info);
        OutputStream out = new FileOutputStream(file);
        encodeMap(metainfo, out);
        out.close();
    }

}
